﻿using FantasyWereld.Common;
using FantasyWereld.Data;
using FantasyWereld.DataModel;
using FantasyWereld.Views;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Windows.ApplicationModel;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Windows.Data.Xml.Dom;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Notifications;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Web.Syndication;

namespace FantasyWereld.Code
{
    class Utility
    {
        private static TileUpdater updater;

        #region GenerateTile
        /// <summary>
        /// Generate live tile based upon the specified review
        /// </summary>
        public static void GenerateTile(FWDataItem item)
        {
            if (updater == null)
            {
                updater = TileUpdateManager.CreateTileUpdaterForApplication();
                updater.EnableNotificationQueue(true);
            }

            var tileXml = buildCombinedTileXml(item, "logo");

            TileNotification tileNotification = new TileNotification(tileXml);

            updater.Update(tileNotification);
        }
        #endregion

        #region GenerateFantasyWereldTile
        /// <summary>
        /// Generate standard FantasyWereld tile using the logo
        /// </summary>
        public static async Task GenerateFantasyWereldTile()
        {
            try
            {
                if (updater == null)
                {
                    updater = TileUpdateManager.CreateTileUpdaterForApplication();
                    updater.EnableNotificationQueue(true);
                }

                // Determine source folder location
                StorageFolder sourceFolder = Package.Current.InstalledLocation;

                // Determine source filename
                StorageFile sourceFilename = await sourceFolder.GetFileAsync(Constants.XML_FILE_DIR + "\\" + Constants.XML_TILE);

                XmlLoadSettings loadSettings = new XmlLoadSettings();
                loadSettings.ProhibitDtd = false;
                loadSettings.ResolveExternals = false;

                var tileXml = await XmlDocument.LoadFromFileAsync(sourceFilename, loadSettings);

                TileNotification tileNotification = new TileNotification(tileXml);

                //tileNotification.ExpirationTime = DateTimeOffset.UtcNow.AddSeconds(10);

                updater.Update(tileNotification);
            }
            catch
            {
            }
        }
        #endregion

        #region buildCombinedTileXml
        /// <summary>
        /// Build combined tile XML
        /// </summary>
        /// <param name="item">Pass FWDataItem object</param>
        /// <param name="brandingValue">Allowd values "none", "logo" or "name"</param>
        protected static XmlDocument buildCombinedTileXml(FWDataItem item, string brandingValue)
        {
            var tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWide310x150ImageAndText01);
            if (item.Author != "")
                tileXml.GetElementsByTagName("text")[0].InnerText = item.Author + " : " + item.Title;
            else
                tileXml.GetElementsByTagName("text")[0].InnerText = item.Title;

            XmlNodeList tileImageAttributes = tileXml.GetElementsByTagName("image");
            ((XmlElement)tileImageAttributes[0]).SetAttribute("src", item.ImageLink);
            ((XmlElement)tileImageAttributes[0]).SetAttribute("alt", item.UniqueId);

            XmlDocument squareTileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileSquare150x150PeekImageAndText04);
            if (item.Author != "")
                squareTileXml.GetElementsByTagName("text")[0].InnerText = item.Author + " : " + item.Title;
            else
                squareTileXml.GetElementsByTagName("text")[0].InnerText = item.Title; 
            XmlNodeList squareTileImageAttributes = squareTileXml.GetElementsByTagName("image");
            ((XmlElement)squareTileImageAttributes[0]).SetAttribute("src", item.ImageLink);
            ((XmlElement)squareTileImageAttributes[0]).SetAttribute("alt", item.UniqueId);

            // Removes logo from lower left-hand corner of tile
            // "none"
            // "logo"
            // "name"
            var sqTileBinding = (XmlElement)squareTileXml.GetElementsByTagName("binding").Item(0);
            sqTileBinding.SetAttribute("branding", brandingValue);

            IXmlNode node = tileXml.ImportNode(squareTileXml.GetElementsByTagName("binding").Item(0), true);
            tileXml.GetElementsByTagName("visual").Item(0).AppendChild(node);

            return tileXml;
        }
        #endregion

        #region CleanupNewLines
        /// <summary>
        /// Remove unwanted double new line charactersfrom input string
        /// </summary>
        public static string CleanupNewLines(string source)
        {
            String cleanedupString = "";
            String prevDescriptionPart = "";
            String curDescriptionPart = "";
            bool startsWithLowerCase = false;
            bool endsCorrectly = false;

            while (source.Length > 0)
            {
                if (source.Contains("\n\n"))
                    curDescriptionPart = source.Substring(0, source.IndexOf("\n\n"));
                else
                    curDescriptionPart = source;

                if (prevDescriptionPart == "")
                {
                    // Current part of the description is also the first part of the description
                    // Save the current sentence as the previous part of the description
                    prevDescriptionPart = curDescriptionPart.Trim();
                    if (prevDescriptionPart.Length > 0)
                        // Determine how this part of the description ends
                        if (
                            (prevDescriptionPart.Substring(prevDescriptionPart.Length - 1, 1) == ".") ||
                            (prevDescriptionPart.Substring(prevDescriptionPart.Length - 1, 1) == "!") ||
                            (prevDescriptionPart.Substring(prevDescriptionPart.Length - 1, 1) == "?"))
                            endsCorrectly = true;
                        else
                            endsCorrectly = false;
                }
                else
                {
                    if (curDescriptionPart.Length > 0)
                    {
                        if (curDescriptionPart.Substring(0, 1).ToLower() == curDescriptionPart.Substring(0, 1).ToString())
                            startsWithLowerCase = true;
                        else
                            startsWithLowerCase = false;

                        if (endsCorrectly && startsWithLowerCase)
                            cleanedupString = cleanedupString + prevDescriptionPart + "\n";
                        else
                            if (endsCorrectly && !startsWithLowerCase)
                                cleanedupString = cleanedupString + prevDescriptionPart + "\n\n";
                            else
                                cleanedupString = cleanedupString + prevDescriptionPart + " ";

                        // Store the current part of the description as the previous one
                        prevDescriptionPart = curDescriptionPart.Trim();

                        // Determine how this part of the description ends
                        if (prevDescriptionPart.Length > 0)
                            if (
                                (prevDescriptionPart.Substring(prevDescriptionPart.Length - 1, 1) == ".") ||
                                (prevDescriptionPart.Substring(prevDescriptionPart.Length - 1, 1) == "!") ||
                                (prevDescriptionPart.Substring(prevDescriptionPart.Length - 1, 1) == "?"))
                                endsCorrectly = true;
                            else
                                endsCorrectly = false;
                    }
                }
                // Fetch next part of the description
                if (source.Contains("\n\n"))
                    source = source.Substring(source.IndexOf("\n\n") + 2, source.Length - source.IndexOf("\n\n") - 2);
                else
                    source = "";
            }
            return cleanedupString + curDescriptionPart;
        }
        #endregion

        #region CleanupText
        /// <summary>
        /// Remove unwanted markup from input string
        /// </summary>
        public static string CleanupText(string source)
        {
            // Cleanup start of content when needed
            if (source.StartsWith("\n"))
                source = source.Substring(1, source.Length - 1);
            return source
                .Replace("\\'", "'")
                .Replace("\r", " ")
                .Replace("\n", " ")
                .Replace("   ", " ")
                .Replace("  ", " ");
        }
        #endregion

        #region StripHTMLTags
        /// <summary>
        /// Remove HTML tags from string using char array.
        /// </summary>
        public static string StripHTMLTags(string source)
        {
            if (source == null)
            {
                return "";
            }

            char[] array = new char[source.Length];
            int arrayIndex = 0;
            bool inside = false;

            for (int i = 0; i < source.Length; i++)
            {
                char let = source[i];
                if (let == '<')
                {
                    inside = true;
                    continue;
                }
                if (let == '>')
                {
                    inside = false;
                    continue;
                }
                if (!inside)
                {
                    array[arrayIndex] = let;
                    arrayIndex++;
                }
            }
            return new string(array, 0, arrayIndex);
        }
        #endregion

        #region ReplaceSpecialCodes
        public static string ReplaceSpecialCodes(string content)
        {
            // Interpret certain tags as new line characters
            return content
                .Replace("\r", "\n")
                .Replace("\n<br />\n", "\n")
                .Replace("<br>", "\n")
                .Replace("<br />\n", "\n")
                .Replace("<br />", "\n")
                .Replace("</p>", "\n")
                .Replace("<p>", "\n")
                .Replace("&nbsp;", " ")
                .Replace("&euml;", "ë")
                .Replace("&iuml;", "ï")
                .Replace("&eacute;", "é")
                .Replace("&uuml;", "ü")
                .Replace("&amp;", "&")
                .Replace("&#038;", "&")
                .Replace("&#39;", "'")
                .Replace("&#8211;", "-")
                .Replace("&#8216;", "‘")
                .Replace("&#8217;", "’")
                .Replace("&#8220;", "“")
                .Replace("&#8221;", "”")
                .Replace("&#8230;", "...")
                .Replace("â", "“")
                .Replace("Ã¯", "ï")
                .Replace("Â", "");
        }
        #endregion

        #region CleanupHTML
        /// <summary>
        /// Cleanup HTML
        /// </summary>
        public static string CleanupHTML(string content)
        {
            // Interpret certain tags as new line characters
            content = ReplaceSpecialCodes(content);

            // Strip remaining HTML
            content = Utility.StripHTMLTags(content);

            char[] contentArray = content.ToCharArray();
            char specialChar = '\u0085';
            if (contentArray.Contains(specialChar))
            {
                string resultContent = "";
                foreach (var singleChar in contentArray)
                {
                    if (singleChar == specialChar)
                        resultContent += "...";
                    else
                        resultContent += singleChar;
                }
                content = resultContent;
            }

            // Cleaunup remaining content
            content = content
                .Replace("     ", " ")
                .Replace("    ", " ")
                .Replace("   ", " ")
                .Replace("  ", " ")
                .Replace("	", "")         // TAB
                .Replace(" \n", "\n")
                .Replace("\n\n\n\n\n\n\n", "\n\n")
                .Replace("\n\n\n\n\n\n", "\n\n")
                .Replace("\n\n\n\n\n", "\n\n")
                .Replace("\n\n\n\n", "\n\n")
                .Replace("\n\n\n", "\n\n")
                .Replace("\n ", "\n");

            // Cleanup start of content
            if (content.StartsWith("\n\n\n"))
                content = content.Substring(3, content.Length - 3);
            if (content.StartsWith("\n\n"))
                content = content.Substring(2, content.Length - 2);
            if (content.StartsWith("\n"))
                content = content.Substring(1, content.Length - 1);

            // Cleanup end of content
            if (content.EndsWith("\n\n\n"))
                content = content.Substring(0, content.Length - 3);
            if (content.EndsWith("\n\n"))
                content = content.Substring(0, content.Length - 2);
            if (content.EndsWith("\n"))
                content = content.Substring(0, content.Length - 1);
            return content;
        }
        #endregion

        #region GetUriLocalImageAsync
        /// <summary>
        /// Copies an image from the internet (http protocol) locally to the AppData LocalFolder.
        /// This is used by some methods (like the SecondaryTile constructor) that do not support 
        /// referencing images over http but can reference them using the ms-appdata protocol.  
        /// </summary>
        /// <param name="internetUri">The path (URI) to the image on the internet</param>
        /// <param name="uniqueName">A unique name for the local file</param>
        /// <returns>Path to the image that has been copied locally</returns>
        public static async Task<Uri> GetUriLocalImageAsync(string internetUri, string uniqueName)
        {
            if (string.IsNullOrEmpty(internetUri))
                return null;
            
            using (var response = await HttpWebRequest.CreateHttp(internetUri).GetResponseAsync())
            {
                using (var stream = response.GetResponseStream())
                {
                    var desiredName = string.Format("{0}", uniqueName);
                    var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(desiredName, CreationCollisionOption.ReplaceExisting);

                    using (var filestream = await file.OpenStreamForWriteAsync())
                    {
                        await stream.CopyToAsync(filestream);
                        return new Uri(string.Format("ms-appdata:///local/{0}", desiredName), UriKind.Absolute);
                    }
                }
            }
        }
        #endregion

        #region SetMessageRequest
        /// <summary>
        /// Fills a DataRequest object that can be shared to other applications.  
        /// </summary>
        /// <param name="item">MediaObject item</param>
        /// <param name="request">DataRequest object with initial values</param>
        /// <returns>Filled DataRequest object</returns>
        public static DataRequest SetMessageRequest(FWDataItem item, DataRequest request)
        {
            var resourceLoader = new ResourceLoader();

            request.Data.Properties.Title = resourceLoader.GetString("MessagePropertiesTitle") + ": " + item.Title;
            request.Data.Properties.Description = resourceLoader.GetString("MessagePropertiesDescription");

            // Share Review text
            string textMessage = resourceLoader.GetString("MessageHeader");
            textMessage += "<br /><br />" + resourceLoader.GetString("MessageTitle") + ": " + item.Title;
            textMessage += "<br />" + resourceLoader.GetString("MessageAuthor") + ": " + item.Author;
            textMessage += "<br />" + resourceLoader.GetString("MessagePublisher") + ": " + item.Publisher;
            textMessage += "<br />" + resourceLoader.GetString("MessageISBN") + ": " + item.ISBN;
            textMessage += "<br />" + resourceLoader.GetString("MessageEdition") + ": " + item.Edition;
            textMessage += "<br />" + resourceLoader.GetString("MessageDatePublished") + ": " + item.PublishDate;
            textMessage += "<br />" + resourceLoader.GetString("MessagePrice") + ": " + item.Price;
            if (!String.IsNullOrEmpty(item.Age))
                textMessage += "<br />" + resourceLoader.GetString("MessageAge") + ": " + item.Age;
            textMessage += "<br /><br />" + resourceLoader.GetString("MessageShortDescription") + ": " + item.Description;

            textMessage += "<br /><br />" + resourceLoader.GetString("MessageDirectLink") + ": <a href='" + item.Link + "'>" + item.Link + "</a>";
            textMessage += "<br /><img src=\"" + item.ImageLink + "\" />";
            textMessage += "<br />" + resourceLoader.GetString("MessageReviewer") + ": " + item.Reviewer;
            if (item.StarsLink.Length > 0)
                textMessage += "<br /><br /><img src=\"http://www.fantasywereld.nl/wp-content/themes/FantasyWereld/images/" + item.StarsLink.Substring(item.StarsLink.IndexOf("ges/") + 4, item.StarsLink.Length - item.StarsLink.IndexOf("ges/") - 4) + "\" />";
            
            request.Data.SetHtmlFormat(HtmlFormatHelper.CreateHtmlFormat(textMessage)); // when sending HTML images can't be added

            // This is not working for the mail Application, but we add it in for other apps
            var reference = RandomAccessStreamReference.CreateFromUri(new Uri(item.ImagePath.AbsoluteUri));
            request.Data.Properties.Thumbnail = reference;
            request.Data.SetBitmap(reference);

            return request;
        }
        #endregion

        #region GetRSSFeedAsync
        public static async Task<IEnumerable<RSSChannel>> GetRSSFeedAsync(string url)
        {
            var client = new HttpClient();
            client.MaxResponseContentBufferSize = Constants.MAX_RESPONSE_BUFFERSIZE;

            var response = await client.GetAsync(new Uri(url));
            var stream = await response.Content.ReadAsStreamAsync();
            
            return getChannelQuery(XDocument.Load(new StreamReader(stream)));
        }
        #endregion

        #region getChannelQuery
        private static IEnumerable<RSSChannel> getChannelQuery(XDocument xdoc)
        {
            return from channels in xdoc.Descendants("channel")
                   select new RSSChannel
                   {
                       Title = channels.Element("title") != null ? channels.Element("title").Value : "",
                       Link = channels.Element("link") != null ? channels.Element("link").Value : "",
                       Description = channels.Element("description") != null ? channels.Element("description").Value : "",
                       Items = from items in channels.Descendants("item")
                               select new RSSItem
                               {
                                   Title = items.Element("title") != null ? items.Element("title").Value : "",
                                   Link = items.Element("link") != null ? items.Element("link").Value : "",
                                   Description = items.Element("description") != null ? items.Element("description").Value : "",
                                   pubDate = items.Element("pubDate") != null ? items.Element("pubDate").Value : "",
                                   Guid = (items.Element("guid") != null ? items.Element("guid").Value : "")
                               }
                   };
        }
        #endregion

        #region ReadXMLDocumentFromFileAsync
        public static async Task<XmlDocument> ReadXMLDocumentFromFileAsync(string fileName)
        {
            try
            {
                // Determine source folder location
                StorageFolder sourceFolder = ApplicationData.Current.LocalFolder;

                // Determine source filename
                StorageFile sourceFilename = await sourceFolder.GetFileAsync(fileName);

                XmlLoadSettings loadSettings = new XmlLoadSettings();
                loadSettings.ProhibitDtd = false;
                loadSettings.ResolveExternals = false;

                return await XmlDocument.LoadFromFileAsync(sourceFilename, loadSettings);
            }
            catch
            {
                return null;
            }
        }
        #endregion

        #region ReloadAppData
        public static async Task ReloadAppData()
        {
            await FWDataSource.WriteItemsToXMLAsync(Constants.XML_REVIEWS_FILE_NAME, FWDataSource.GetAllFeedInfoSortedOnAddedDescending());
            await FWDataSource.WriteItemsToXMLAsync(Constants.XML_AUTHORS_FILE_NAME, FWDataSource.GetAllAuthorsSortedOnAuthor());
            await FWDataSource.WriteItemsToXMLAsync(Constants.XML_BOOKS_FILE_NAME, FWDataSource.GetAllBooks());
            await FWDataSource.WriteSuggestionsToFileAsync();

            // Clear memory and reload app
            FWDataSource.Clear();

            await FWDataSource.LoadDataAsync();

            try
            {
                // Read books from disk and fetch images
                FWDataSource.ReadReviewsFromDisk(false);
            }
            catch
            {
                // On error switch to offline mode
                FWDataSource.ReadReviewsFromDisk(true);
            }

            try
            {
                // Read authors from disk
                FWDataSource.ReadAuthorsFromDisk(false);
            }
            catch
            {
                // On error switch to offline mode
                FWDataSource.ReadAuthorsFromDisk(true);
            }

            // Store number of Items
            Windows.Storage.ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
            localSettings.Values["numberOfItems"] = FWDataSource.GetItemCount;

            // Process the RSS Feeds asynchronously
            FWDataSource.LoadRemoteDataAsync();

            // Add a Frame control to the window
            var rootFrame = new Frame();
            SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

            if (!rootFrame.Navigate(typeof(MainPage)))
            {
                throw new Exception("Failed to create initial page");
            }

            // Place the frame in the current Window and ensure that it is active
            Window.Current.Content = rootFrame;
        }
        #endregion

        #region SendNotification
        public static void SendNotification(string titel, string message, int type)
        {
            bool showNews = Convert.ToBoolean(ApplicationData.Current.RoamingSettings.Values["ShowPopupNews"]);
            if (showNews)
            {
                string notificationImage = Constants.NOTIFICATION_IMAGE;

                switch (type)
                {
                    case Constants.NOTIFICATION_AUTHORS:
                        notificationImage = Constants.NOTIFICATION_AUTHORS_IMAGE;
                        break;
                    case Constants.NOTIFICATION_BOOKS:
                        notificationImage = Constants.NOTIFICATION_BOOKS_IMAGE;
                        break;
                    case Constants.NOTIFICATION_REVIEWS:
                        notificationImage = Constants.NOTIFICATION_REVIEWS_IMAGE;
                        break;
                    case Constants.NOTIFICATION_STORIES:
                        notificationImage = Constants.NOTIFICATION_STORIES_IMAGE;
                        break;
                }
                var notificationXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText02);
                var toeastElement = notificationXml.GetElementsByTagName("text");
                toeastElement[0].AppendChild(notificationXml.CreateTextNode(titel));
                toeastElement[1].AppendChild(notificationXml.CreateTextNode(message));

                var imageElement = notificationXml.GetElementsByTagName("image");
                imageElement[0].Attributes[1].NodeValue = notificationImage;

                var toastNotification = new ToastNotification(notificationXml);
                ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
            }
        }
        #endregion
    }
}
